package com.cheesmo.nzb.codec;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.StringTokenizer;
import java.util.logging.Logger;

public class YEncDecoder
		implements YEncConstants
{
	String destDir;
	String fileToDecode = null;
	boolean delete = false;
	InputStream toDecode;
	static final String CLASSNAME = YEncDecoder.class.getName();
	private static Logger logger = Logger.getLogger(CLASSNAME);

	public YEncDecoder(String fileToDecode, String destDir, boolean deleteOnCompletion) {
		logger.entering(CLASSNAME, "YEncDecoder");
		this.fileToDecode = fileToDecode;
		this.destDir = destDir;
		this.delete = deleteOnCompletion;
		logger.exiting(CLASSNAME, "YEncDecoder");
	}

	public YEncDecoder(InputStream toDecode, String destDir) {
		this.toDecode = toDecode;
		this.destDir = destDir;
	}

	public String decode()
			throws DecodingException
	{
		boolean decoding = false;
		try {
			BufferedInputStream input = null;
			if (this.fileToDecode != null)
				input = new BufferedInputStream(new FileInputStream(this.fileToDecode), 16384);
			else {
				input = new BufferedInputStream(this.toDecode);
			}
			byte[] bytes = new byte[512];
			int bytesRead = readLine(input, bytes);
			String line = new String(bytes);
			String outputName = null;
			OutputStream out = null;

			while (line != null) {
				if (line.startsWith("=ybegin")) {
					if (decoding) {
						throw new DecodingException("=ybegin not expected.");
					}
					decoding = true;
					if (out == null)
					{
						YBegin ybegin = new YBegin(line);
						outputName = ybegin.name;
						out = new BufferedOutputStream(new FileOutputStream(new File(this.destDir, outputName)));
					}
				} else if (line.startsWith("=yend")) {
					if (!decoding) {
						throw new IOException("=yend not expected.");
					}
					decoding = false;
				} else if (!line.startsWith("=ypart"))
				{
					if (decoding)
						decodeLine(bytes, bytesRead, out);
				}
				bytesRead = readLine(input, bytes);
				if ((bytes != null) && (bytesRead > 1) && (bytes[0] == 61) && (bytes[1] == 121)) {
					line = new String(bytes, 0, bytesRead);
				} else {
					if (bytesRead == -1) {
						input.close();

						if (out != null) {
							out.flush();
							out.close();
						}

						if (this.delete) {
							System.out.println("Removing " + this.fileToDecode + ":  " + new File(this.fileToDecode).delete());
						}
						return outputName;
					}
					line = "";
				}
			}
		} catch (FileNotFoundException e)
		{
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	private int readLine(BufferedInputStream input, byte[] buffer)
			throws IOException
	{
		int prev = -1;
		for (int i = 0; i < buffer.length; i++) {
			int read = input.read();
			if ((read == 10) || (read == 0)) {
				if (i == 0)
					return 0;
				if (prev == 13) {
					return i - 1;
				}
				return i;
			}

			prev = read;
			buffer[i] = (byte) read;
		}

		return -1;
	}

	private void decodeLine(byte[] bytes, int length, OutputStream out)
			throws IOException
	{
		boolean escape = false;
		for (int i = 0; i < length; i++) {
			int b = bytes[i];
			if ((!escape) && (b == 61)) {
				escape = true;
			} else if (escape) {
				out.write(b - 64 - 42);
				escape = false;
			} else {
				out.write(b - 42);
			}
		}
	}

	public static void main(String[] args)
			throws DecodingException
	{
		String[] fileNames = { "00000020.ntx", "00000021.ntx" };
		try {
			SplitFileInputStream sfis = new SplitFileInputStream("C:/", fileNames, true);
			YEncDecoder decoder = new YEncDecoder(sfis, "C:/");
			decoder.decode();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	public static class YBegin
	{
		int line = -1;
		int size = -1;
		int total = -1;
		String name = null;

		public YBegin(String text) throws DecodingException
		{
			StringTokenizer tokenizer = new StringTokenizer(text);

			if (!tokenizer.nextToken().equals("=ybegin")) {
				throw new DecodingException("Not valid =ybegin");
			}
			do
			{
				String token = tokenizer.nextToken();
				if (token.startsWith("line="))
					this.line = Integer.parseInt(token.substring("line=".length()));
				else if (token.startsWith("size="))
					this.size = Integer.parseInt(token.substring("size=".length()));
				else if (token.startsWith("total="))
					this.total = Integer.parseInt(token.substring("total=".length()));
			} while (tokenizer.hasMoreTokens());

			int index = text.indexOf("name=");
			if (index > -1)
				this.name = text.substring(index + "name=".length());
			else
				throw new DecodingException("No name specified in =ybegin");
		}

		public String toString()
		{
			YEncDecoder.logger.entering(YEncDecoder.CLASSNAME, "toString");
			StringBuffer toReturn = new StringBuffer();
			toReturn.append("[ybegin]");
			if (this.name != null) {
				toReturn.append(" Name: " + this.name);
			}
			if (this.line > -1) {
				toReturn.append(", Line: " + this.line);
			}
			if (this.size > -1) {
				toReturn.append(", Size: " + this.size);
			}
			if (this.total > -1) {
				toReturn.append(", Total: " + this.total);
			}
			YEncDecoder.logger.exiting(YEncDecoder.CLASSNAME, "toString");
			return toReturn.toString();
		}
	}

	public static class YEnd
	{
		int size = -1;
		String crc32 = null;

		public YEnd(String text) throws DecodingException
		{
			StringTokenizer tokenizer = new StringTokenizer(text);

			if (!tokenizer.nextToken().equals("=yend")) {
				throw new DecodingException("Not valid =yend");
			}
			do
			{
				String token = tokenizer.nextToken();
				if (token.startsWith("size="))
					this.size = Integer.parseInt(token.substring("size=".length()));
				else if (token.startsWith("crc32="))
					this.crc32 = token.substring("crc32=".length());
			} while (tokenizer.hasMoreTokens());
		}

		public String toString()
		{
			StringBuffer toReturn = new StringBuffer();
			toReturn.append("[yend]");
			if (this.size > -1)
				toReturn.append(" Size: " + this.size);
			if (this.crc32 != null)
				toReturn.append(", Crc32: " + this.crc32);
			return toReturn.toString();
		}
	}
}
